﻿using System;
using System.Collections.Generic;
using System.IO;
using LuaVM.Core.Builtin;

namespace LuaVM.Core
{
    /// <summary>
    /// 定制Lua全局环境对象
    /// </summary>
    public class LuaEnv
    {
        /// <summary>
        /// 用以重定向Lua环境中的print输出的委托定义
        /// </summary>
        /// <param name="items">在Lua环境中生成的输出文字内容</param>
        public delegate void LuaPrintDelegate(string[] items);

        /// <summary>
        /// 重定向Lua环境中的print输出
        /// </summary>
        public LuaPrintDelegate LuaPrint { get; set; }

        /// <summary>
        /// 使用字符串路径直接向全局环境赋值/取值
        /// </summary>
        /// <param name="path">路径</param>
        /// <returns>全局值</returns>
        public object this[string path]
        {
            get
            {
                return LuaTable.ConvertForGet(m_lua[path]);
            }

            set
            {
                m_lua[path] = LuaTable.ConvertForSet(value);
            }
        }

        /// <summary>
        /// package.path的内置格式
        /// </summary>
        public const string PackagePathFormat = ".\\?.lua; {0}\\lua\\?.lua; {0}\\lua\\?\\init.lua; {0}\\?.lua; {0}\\?\\init.lua";

        /// <summary>
        /// 构造函数
        /// </summary>
        public LuaEnv()
		{
            KopiLua.Lua.LuaPrint = DoPrint;
            NewTable("plugins");

            m_builtin.Add(new GlobalFuncs(this));
            m_builtin.Add(new OSFuncs(this));
            m_builtin.Add(new MathFuncs(this));
            m_builtin.Add(new ProcessFuncs(this));
        }

        /// <summary>
        /// 装载一个插件
        /// </summary>
        /// <param name="plugin">插件对象</param>
        public void InstallPlugin(LuaPlugin plugin)
		{
            plugin.Attach(this);
        }

        /// <summary>
        /// 卸载一个插件
        /// </summary>
        /// <param name="plugin">插件对象</param>
        public void UninstallPlugin(LuaPlugin plugin)
        {
            plugin.Detach();            
        }

        /// <summary>
        /// 创建一个Lua表对象
        /// </summary>
        /// <param name="path">储存路径，如果为null则使用临时路径（创建的对象无法通过GetTable获取）</param>
        /// <returns>创建的LuaTable对象，创建失败则返回null</returns>
        public LuaTable NewTable(string path = null)
        {
            return LuaTable.Create(m_lua, path);
        }

        /// <summary>
        /// 获取某个Lua表
        /// </summary>
        /// <param name="path">路径</param>
        /// <returns>LuaTable对象，如果不存在则返回null</returns>
        public LuaTable GetTable(string path)
		{
            return LuaTable.Find(m_lua, path);
        }

        /// <summary>
        /// 将一个静态函数注册入Lua环境
        /// </summary>
        /// <typeparam name="T">静态函数所属的class</typeparam>
        /// <param name="name">静态函数名称</param>
        /// <param name="path">函数在Lua环境中的路径，null则注册于_G中并使用原函数名</param>
        public void RegisterFunction<T>(string name, string path = null)
        {            
            m_lua.RegisterFunction(path ?? name, null, typeof(T).GetMethod(name));
        }

        /// <summary>
        /// 将一个类成员函数（方法）注册入Lua环境
        /// </summary>
        /// <typeparam name="T">成员函数所属的class</typeparam>
        /// <param name="instance">类实例</param>
        /// <param name="name">成员函数名，必须是定义于委托类中的public成员</param>
        /// <param name="path">函数在Lua环境中的路径，null则注册于_G中并使用原函数名</param>
        public void RegisterFunction<T>(T instance, string name, string path = null)
        {
            m_lua.RegisterFunction(path ?? name, instance, typeof(T).GetMethod(name));
        }

        /// <summary>
        /// 设置package.path
        /// </summary>
        /// <param name="path">路径，null则使用当前程序启动路径</param>
        /// <param name="usePackageFormat">是否使用PackagePathFormat进行额外转化</param>
        public void SetPackagePath(string path, bool usePackageFormat = false)
		{
            if (string.IsNullOrEmpty(path))
			{
                path = AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\');
			}

            if (usePackageFormat)
			{
                path = string.Format(PackagePathFormat, path);
            }

            this["package.path"] = path;
		}

        /// <summary>
        /// 获取当前的package.path
        /// </summary>
        /// <returns></returns>
        public string[] GetPackagePath()
		{
            string path = (this["package.path"] as string) ?? "";
            return path.Split(';');
        }

        /// <summary>
        /// 在当前Lua环境中执行一个脚本文件
        /// </summary>
        /// <param name="filePath">脚本文件路径</param>
        /// <returns>脚本返回值</returns>
        public object[] DoFile(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
			{
                throw new ArgumentException("DoFile: filePath is required.");
			}

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("File not found: " + filePath);
            }

            if (Path.GetExtension(filePath).ToLower() != ".lua")
			{
                throw new FileLoadException("Only *.lua files are acceptable.", filePath);
			}

            // KopiLua DoFile only accepts ANSI files...
            //return m_lua.DoFile(filePath);

            string chunk = File.ReadAllText(filePath);
            string chunkName = MakeShortPath(filePath, 45);
            return m_lua.DoString(chunk, chunkName);           
        }        

        /// <summary>
        /// 在当前Lua环境中执行一段脚本
        /// </summary>
        /// <param name="chunk">Lua脚本</param>
        /// <returns>脚本返回值</returns>
        public object[] DoString(string chunk)
        {
            return m_lua.DoString(chunk);
        }

        /// <summary>
        /// 在当前Lua环境中执行一段脚本
        /// </summary>
        /// <param name="chunk">Lua脚本</param>
        /// <param name="chunkName">Lua脚本标识</param>
        /// <returns>脚本返回值</returns>
        public object[] DoString(string chunk, string chunkName)
        {
            return m_lua.DoString(chunk, chunkName);
        }

        /// <summary>
        /// 寻找并执行当前Lua环境中的main()函数
        /// </summary>
        /// <param name="args">运行参数</param>
        /// <returns>main()函数返回值</returns>
        public object[] Execute(params object[] args)
		{
            LuaInterface.LuaFunction func = this["main"] as LuaInterface.LuaFunction;
            if (func == null)
			{
                throw new EntryPointNotFoundException();
			}

            return func.Call(args);
        }

        /// <summary>
        /// 默认Lua环境的print输出
        /// </summary>
        /// <param name="items"></param>
        private void DoPrint(string[] items)
		{
            if (LuaPrint == null)
			{
                Console.WriteLine(string.Join("\t", items));
			}
            else
			{
                LuaPrint.Invoke(items);
			}
		}

        private static string MakeShortPath(string fullPath, int maxLen)
        {
            if (string.IsNullOrEmpty(fullPath) || fullPath.Length <= maxLen)
            {
                return fullPath;
            }

            string root = Path.GetPathRoot(fullPath) + "...";
            string tail = "\\" + Path.GetFileName(fullPath);
            if (root.Length + tail.Length >= maxLen)
            {
                return root + tail;
            }

            string currentPath = Path.GetDirectoryName(fullPath);
            string parent = Path.GetFileName(currentPath);

            while (root.Length + 1 + parent.Length + tail.Length <= maxLen)
            {
                tail = "\\" + parent + tail;
                currentPath = Path.GetDirectoryName(currentPath);
                parent = Path.GetFileName(currentPath);
            }

            return root + tail;
        }        

        // 内部LuaInterface.Lua对象
        private LuaInterface.Lua m_lua = new LuaInterface.Lua();

        // 内置插件创建后实例存于此处以防GC
        private List<object> m_builtin = new List<object>();
    }    
}
